Vue 响应式原理解析

大家都知道 vue 是双向数据绑定,vue 的双向数据绑定使用的是 v-model 实现。v-model实际上是 input 的事件 和 value 属性值。其最本质的原理是基于 vue 的响应式原理实现。响应式的实现原理是怎么样的呢?让我们接下来一起看看

1. 数据劫持

源码位置:vue/src/core/observer/index.ts
在vue 源码有这么一个函数 defineReactive,其本质是使用Object.defineProperty 方法劫持对象属性,使得在访问和修改属性时能够执行特定的操作

  • get: 用于获取属性值,同时进行依赖收集
  • set: 设置新值,触发依赖更新。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
export function defineReactive(
obj: object,
key: string,
val?: any,
customSetter?: Function | null,
shallow?: boolean,
mock?: boolean
) {
// 创建依赖收集
const dep = new Dep()

// 获取属性值描述,如果是只读属性,直接返回
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}

// cater for pre-defined getter/setters
// 预定义 getter/setter
const getter = property && property.get
const setter = property && property.set
if (
(!getter || setter) &&
(val === NO_INITIAL_VALUE || arguments.length === 2)
) {
val = obj[key]
}

let childOb = !shallow && observe(val, false, mock)
// 使用 Object.defineProperty 定义响应式属性
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// get 方式 用于获取属性值,在获取值的时候进行依赖收集
get: function reactiveGetter() {

// 是否有getter, 有就直接调用 getter
const value = getter ? getter.call(obj) : val
if (Dep.target) {
if (__DEV__) {
dep.depend({
target: obj,
type: TrackOpTypes.GET,
key
})
} else {
dep.depend()
}
// 如果有子响应式对象,依赖收集
if (childOb) {
childOb.dep.depend()
//如果是数组类型,需要对数组的每一项进行依赖收集
if (isArray(value)) {
dependArray(value)
}
}
}
return isRef(value) && !shallow ? value.value : value
},
// set 方式 用于设置属性值,同时触发依赖更新
set: function reactiveSetter(newVal) {
// 先使用 getter 获取旧值
const value = getter ? getter.call(obj) : val
// 新旧值一致时,直接返回
if (!hasChanged(value, newVal)) {
return
}
if (__DEV__ && customSetter) {
customSetter()
}
// 如果有 setter 调用 setter 进行更新
if (setter) {
setter.call(obj, newVal)
} else if (getter) {
// #7981: for accessor properties without setter
return
} else if (!shallow && isRef(value) && !isRef(newVal)) {
value.value = newVal
return
} else {
val = newVal
}
childOb = !shallow && observe(newVal, false, mock)
if (__DEV__) {
dep.notify({
type: TriggerOpTypes.SET,
target: obj,
key,
newValue: newVal,
oldValue: value
})
} else {
// 依赖更新
dep.notify()
}
}
})

return dep
}

其中 observe函数,递归遍历对象属性,为每个属性创建一个dep实例,通过Object.defineProperty 为每个属性设置 getter 和 setter。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function observe(obj) {
if (typeof obj !== 'object' || obj === null) {
return
}

Object.keys(obj).forEach(key => {
let internalValue = obj[key]

const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
dep.depend()
return internalValue
},
set(newValue) {
if (newValue !== internalValue) {
internalValue = newValue
dep.notify()
}
}
})
observe(internalValue)
})
}

2. 依赖追踪

vue 的响应式系统引入了一个重要概念 – 依赖追踪。每个响应式属性都会关联一个 Dep(依赖)对象,它复制收集所有依赖于该属性的观察者(Watcher)。
Alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 依赖管理器 Dep
class Dep {
constructor() {
this.subscribers = new Set();
}

depend() {
if (activeWatcher) {
this.subscribers.add(activeWatcher);
}
}

notify() {
this.subscribers.forEach(subscribe => {
subscribe.update();
})
}
}

当属性被访问时,会调用 dep.depend(),将当前的观察者添加到依赖集合中。当属性被修改时,会调用 dep.notify(),通知所有依赖的观察者进行更新。

3.观察者模式

观察者模式是vue 响应式系统的核心。通过创建观察者对象,订阅并响应数据的变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Watcher {
constructor(updateFn) {
this.updateFn = updateFn
}

update() {
this.updateFn()
}

run() {
// 将当前实例设置活动观察者,执行更新后重置。
activeWatcher = this
this.update()
activeWatcher = null
}
}

总结:

Vue 采用 数据劫持 结合 发布者-订阅者模式的方式来实现数据的响应式,通过Object.defineProperty 来劫持数据的 setter 和 getter,在数据变动时发布消息给订阅者,订阅者收到消息后进行相应的处理。

参考链接:
https://tsejx.github.io/vue-guidebook/infrastructure/vue2/reactivity/#%E6%80%BB%E7%BB%93